/*
 * FitnessComputer.h
 *
 *  Created on: 4.1.2013
 *      Author: shadius
 */

#ifndef FITNESSCOMPUTER_H_
#define FITNESSCOMPUTER_H_
#include <iostream>
#include "INDIVIDUUM.h"
#include "EvoAlghorithm.h"
#include "IFastDataApprox.h"
#include "Checker.h"
#include "tbb/blocked_range.h"
#include <tbb/mutex.h>
#include <limits>
class FitnessComputer {

	LPAPPROXPOINT const bgAPPROXPOINTS;
	LPAPPROXPOINT const igAPPROXPOINTS;
	int len;
	LINDIVIDUUM const population;
	double *m_fitness;
	shared_ptr<IFastDataApprox> p_BGValues;
	shared_ptr<IFastDataApprox> p_IGValues;
	shared_ptr<vector<double>> p_ECGValues;
	INPUT_PARAMS params;
	Checker checker;
	THREAD_RESULTS * const result;
	tbb::mutex *mutex;

public:
	FitnessComputer(LPAPPROXPOINT const bgAPPROXPOINTS,
			LPAPPROXPOINT const igAPPROXPOINTS, int len,
			LINDIVIDUUM const population, double * m_fitness,
			const shared_ptr<IFastDataApprox> &BGValues,
			const shared_ptr<IFastDataApprox> &IGValues,
			const shared_ptr<vector<double>> p_ECGValues, INPUT_PARAMS params,
			THREAD_RESULTS *result, tbb::mutex *mutex) :
			bgAPPROXPOINTS(bgAPPROXPOINTS), igAPPROXPOINTS(igAPPROXPOINTS), len(
					len), population(population), m_fitness(m_fitness), p_BGValues(
					BGValues), p_IGValues(IGValues), p_ECGValues(p_ECGValues), params(
					params), checker(Checker(params)), result(result), mutex(
					mutex) {
	}

	/**
	 * serial verze výpočtu fitness, do struct result ukládá nalezenou
	 * minimální a maximální nalezenou fitness
	 */
	void serial(const int from, const int to) const {

		for (int i = from; i != to; i++) {
			double fitnessValue = fitness(population[i], bgAPPROXPOINTS,
					igAPPROXPOINTS, len);
			m_fitness[i] = fitnessValue;

			if (result->bestFitness < fitnessValue) {
				result->bestFitness = fitnessValue;
				result->bestFitnessIdx = i;
			} else if (result->minFitness > fitnessValue) {
				result->minFitness = fitnessValue;

			}

		}
	}
	/**
	 * paralel verze výpočtu fitness pro TBB, do struct result ukládá nalezenou
	 * minimální a maximální nalezenou fitness
	 */
	void operator()(const tbb::blocked_range<int>& r) const {

		//vytvoříme localFitness - mutex budeme zamykat až na konci
		double localBestFitness = 0;
		double localbestFitnessIdx = 0;
		double localMinFitness = numeric_limits<double>::infinity();
		int end = r.end();
		for (int i = r.begin(); i != end; i++) {
			double fitnessValue = fitness(population[i], bgAPPROXPOINTS,
					igAPPROXPOINTS, len);
			m_fitness[i] = fitnessValue;
			if (fitnessValue > localBestFitness) {
				localBestFitness = fitnessValue;
				localbestFitnessIdx = i;
			} else if (fitnessValue < localMinFitness) {
				localMinFitness = fitnessValue;
			}
		}
		{
			tbb::mutex::scoped_lock lock(*mutex);
			if (result->bestFitness < localBestFitness) {
				result->bestFitness = localBestFitness;
				result->bestFitnessIdx = localbestFitnessIdx;
			} else if (result->minFitness > localMinFitness) {
				result->minFitness = localMinFitness;

			}
		}
	}

private:
	/**
	 * Výpočet fitness pro jednoho člena populace
	 */
	double fitness(INDIVIDUUM individuum, LPAPPROXPOINT bgAPPROXPOINTS,
			LPAPPROXPOINT igAPPROXPOINTS, int len) const {

		double p = individuum.p;
		double cg = individuum.cg;
		double c = individuum.c;
		double dt = individuum.dt;
		double k = individuum.k;

		int count = 0;
		double sum = 0;

		if (!countAverage(bgAPPROXPOINTS, igAPPROXPOINTS, p, cg, c, dt, k, len,
				&sum, &count)) {
			return 0;
		}
		//čím větší fitness, tím lepší výsledek
		return 1.0 / (sum / (double) count);

	}

	/**
	 * Spočítat průměrnou odchylku levé a pravé strany rovnice
	 */
	bool countAverage(LPAPPROXPOINT bgAPPROXPOINTS,
			LPAPPROXPOINT igAPPROXPOINTS, double p, double cg, double c,
			double dt, double k, int len, double* sum, int* count) const {

		double localSum = 0;
		int localCount = 0;
		for (int i = 0; i < len; i++) {

			int igPoint = 0;
			double ecg = (*p_ECGValues)[i];
			//kontrolujeme hodnoty (teoreticky by měly být vždy v mezích,
			//protože je kontrolujeme i při mutaci
			if (!checker.checkRangeC(c) || !checker.checkRangeCG(cg)
					|| !checker.checkRangeP(p) || !checker.checkRangeDT(dt)
					|| !checker.checkTimeJumpRange(k, dt, ecg)) {
				return false;
			}
			p_BGValues->GetPrecedingPoint(
					TimeUtils::convertSecToWin(i) + dt + k * dt * ecg,
					&igPoint);

			if (igPoint < len && igPoint > 0) {
				double dif = fabs(
						p * bgAPPROXPOINTS[i].y + cg * bgAPPROXPOINTS[i].y * ecg
								+ c - igAPPROXPOINTS[igPoint].y);

				localCount++;
				localSum += dif;
			}

		}
		*count = localCount;
		*sum = localSum;
		return true;
	}
}
;

#endif /* FITNESSCOMPUTER_H_ */
